use crate::chess::*;
use ahash::AHashMap;

// 棋谱
#[derive(PartialEq, Debug)]
pub struct Book {
    // 棋谱名称
    pub name: String,

    // 棋谱描述
    pub description: String,

    // 起始局面的编号，0表示新棋局，否则为残局棋谱
    pub start: SituationId,

    // 轮哪一方走
    pub turn: Color,

    //收藏的局面列表
    pub named_situations: Vec<(SituationId, Color, String)>,

    // 局面列表，保持以SituationId升序排列，用于二分法查询
    pub situations: Vec<Situation>,
}

impl Book {
    pub fn new(name: String) -> Book {
        Book {
            name,
            description: String::new(),
            start: 0,
            turn: Color::Red,
            named_situations: vec![],
            situations: vec![Situation {
                id: 0,
                pieces: Situation::encode(&Board::new().pieces),
                steps: vec![],
            }],
        }
    }
    // pub fn load(file: impl AsRef<Path>) -> Result<Book> {
    //     Book::read_from_file(file).context("Failed to load {file}")
    // }

    // pub fn save(&self, file: impl AsRef<Path>) -> Result<()> {
    //     self.write_to_file(file).context("Failed to save {file}")
    // }

    pub fn create_board(&self, start: SituationId) -> Board {
        if let Some(situation) = self.get_situation(start) {
            Board::with_pieces(&situation.decode())
        } else {
            Board::new()
        }
    }

    pub fn build_dict(&self) -> AHashMap<[u8; 90], SituationId> {
        let mut dict = AHashMap::with_capacity(self.situations.len() + 1000);
        for situation in &self.situations {
            let res = dict.insert(situation.pieces, situation.id);
            if let Some(id) = res {
                println!("Warning: duplicate situations: {id} and {}", situation.id);
            }
        }
        dict
    }

    pub fn get_situation(&self, id: SituationId) -> Option<&Situation> {
        if id < self.situations.len() {
            if self.situations[id].id == id {
                Some(&self.situations[id])
            } else {
                self.situations
                    .binary_search_by_key(&id, |situation| situation.id)
                    .map(|index| &self.situations[index])
                    .ok()
            }
        } else {
            None
        }
    }

    pub fn get_situation_mut(&mut self, id: SituationId) -> Option<&mut Situation> {
        if id < self.situations.len() {
            if self.situations[id].id == id {
                Some(&mut self.situations[id])
            } else {
                self.situations
                    .binary_search_by_key(&id, |situation| situation.id)
                    .map(|index| &mut self.situations[index])
                    .ok()
            }
        } else {
            None
        }
    }

    pub fn get_next_steps(&self, id: SituationId, turn: Color) -> Vec<Step> {
        if let Some(situation) = self.get_situation(id) {
            situation.next_steps(turn)
        } else {
            vec![]
        }
    }

    pub fn find_or_add_situation(
        &mut self,
        dict: &mut AHashMap<[u8; 90], SituationId>,
        board: &Board,
    ) -> SituationId {
        let pieces = Situation::encode(&board.pieces);
        if let Some(id) = dict.get(&pieces) {
            *id
        } else {
            let id = self.situations.len();
            let situation = Situation {
                id,
                pieces,
                steps: vec![],
            };
            self.situations.push(situation);
            dict.insert(pieces, id);
            id
        }
    }

    pub fn add_step(&mut self, prev: SituationId, next: SituationId, from: Pos, to: Pos) -> bool {
        if let Some(situation) = self.get_situation_mut(prev) {
            situation.add_step(from, to, next)
        } else {
            false
        }
    }

    pub fn import_step(&mut self, prev: SituationId, next: SituationId, step: &Step) -> bool {
        if let Some(situation) = self.get_situation_mut(prev) {
            let mut new_step = Step::new(step.from, step.to, next);
            new_step.score = step.score;
            situation.import_step(new_step)
        } else {
            false
        }
    }

    pub fn delete_step(&mut self, id: SituationId, from: Pos, to: Pos) -> bool {
        if let Some(situation) = self.get_situation_mut(id) {
            situation.delete_step(from, to)
        } else {
            false
        }
    }

    pub fn import_book(
        &mut self,
        dict: &mut AHashMap<[u8; 90], SituationId>,
        other: &Book,
    ) -> bool {
        let mut has_new = false;
        for situation in &other.situations {
            let mut board = Board::with_pieces(&situation.decode());
            let prev_id = self.find_or_add_situation(dict, &board);
            for step in &situation.steps {
                let eaten_piece = board.apply_move(step.from, step.to);
                let next_id = self.find_or_add_situation(dict, &board);
                if self.import_step(prev_id, next_id, step) {
                    has_new = true;
                }
                board.revert_move(step.from, step.to, eaten_piece);
            }
        }
        has_new
    }

    pub fn search_situation(&self, pattern: &[(Piece, Pos)]) -> Vec<&Situation> {
        self.situations
            .iter()
            .filter(|situation| situation.contains(pattern))
            .collect::<Vec<_>>()
    }

    pub fn get_named_situation(&self, id: SituationId) -> Option<(&String, Color)> {
        for (sid, turn, name) in &self.named_situations {
            if *sid == id {
                return Some((name, *turn));
            }
        }
        return None;
    }

    pub fn update_situation_name(&mut self, id: SituationId, turn: Color, name: String) {
        for (sid, sturn, sname) in self.named_situations.iter_mut() {
            if *sid == id && *sturn == turn {
                *sname = name;
                return;
            }
        }
        self.named_situations.push((id, turn, name));
    }
}
